﻿// ---------------------------------------------------------------------------------------------------------------------
//   Copyright (c) SRT Solutions 2009. All rights reserved.
// ---------------------------------------------------------------------------------------------------------------------

namespace SRTSolutions.Elevate.FuncExtensions
{
    using global::System;

    /// <summary>
    /// Contains methods for doing partial fucntion application.
    /// </summary>
    public static class PartialApplication
    {
        #region Two Arguments

        /// <summary>
        /// Partially applies the first argument of the specified function.
        /// </summary>
        /// <typeparam name="T1">The type of the first argument.</typeparam>
        /// <typeparam name="T2">The type of the second argument.</typeparam>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="func">The function to partially apply.</param>
        /// <param name="value">The value to apply.</param>
        /// <returns>The curried function with the first value applied</returns>
        public static Func<T2, TResult> ApplyFirst<T1, T2, TResult>(this Func<T1, T2, TResult> func, T1 value)
        {
            if (func == null)
                throw new ArgumentNullException("func", "func is null.");
            if (value == null)
                throw new ArgumentNullException("value", "value is null.");

            return second => func(value, second);
        }

        /// <summary>
        /// Partially applies the second argument of the specified function.
        /// </summary>
        /// <typeparam name="T1">The type of the first argument.</typeparam>
        /// <typeparam name="T2">The type of the second argument.</typeparam>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="func">The function to partially apply.</param>
        /// <param name="value">The value to apply.</param>
        /// <returns>The curried function with the second value applied.</returns>
        public static Func<T1, TResult> ApplySecond<T1, T2, TResult>(this Func<T1, T2, TResult> func, T2 value)
        {
            if (func == null)
                throw new ArgumentNullException("func", "func is null.");
            if (value == null)
                throw new ArgumentNullException("value", "value is null.");

            return first => func(first, value);
        }

        /// <summary>
        /// Partially applies the first argument of the specified action.
        /// </summary>
        /// <typeparam name="T1">The type of the first argument.</typeparam>
        /// <typeparam name="T2">The type of the second argument.</typeparam>
        /// <param name="action">The action to partially apply.</param>
        /// <param name="value">The value to apply.</param>
        /// <returns>The curried action with the fist value applied.</returns>
        public static Action<T2> ApplyFirst<T1, T2>(this Action<T1, T2> action, T1 value)
        {
            if (action == null)
                throw new ArgumentNullException("action", "action is null.");
            if (value == null)
                throw new ArgumentNullException("value", "value is null.");

            return second => action(value, second);
        }

        /// <summary>
        /// Partially applies the second argument of the specified action.
        /// </summary>
        /// <typeparam name="T1">The type of the first argument.</typeparam>
        /// <typeparam name="T2">The type of the second argument.</typeparam>
        /// <param name="action">The action to partially apply.</param>
        /// <param name="value">The value to apply.</param>
        /// <returns>The curried action with the second value applied.</returns>
        public static Action<T1> ApplySecond<T1, T2>(this Action<T1, T2> action, T2 value)
        {
            if (action == null)
                throw new ArgumentNullException("action", "action is null.");
            if (value == null)
                throw new ArgumentNullException("value", "value is null.");

            return first => action(first, value);
        }

        #endregion

        #region Three Arguments

        /// <summary>
        /// Partially applies the first argument of the specified function.
        /// </summary>
        /// <typeparam name="T1">The type of the first argument.</typeparam>
        /// <typeparam name="T2">The type of the second argument.</typeparam>
        /// <typeparam name="T3">The type of the third argument.</typeparam>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="func">The function to partially apply.</param>
        /// <param name="value">The value to apply.</param>
        /// <returns>The curreid function with the first value applied</returns>
        public static Func<T2, T3, TResult> ApplyFirst<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> func, T1 value)
        {
            if (func == null)
                throw new ArgumentNullException("func", "func is null.");
            if (value == null)
                throw new ArgumentNullException("value", "value is null.");

            return (second, third) => func(value, second, third);
        }

        /// <summary>
        /// Partially applies the second argument of the specified function.
        /// </summary>
        /// <typeparam name="T1">The type of the first argument.</typeparam>
        /// <typeparam name="T2">The type of the second argument.</typeparam>
        /// <typeparam name="T3">The type of the third argument.</typeparam>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="func">The function to partially apply.</param>
        /// <param name="value">The value to apply.</param>
        /// <returns>The curried function with the second value applied</returns>
        public static Func<T1, T3, TResult> ApplySecond<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> func, T2 value)
        {
            if (func == null)
                throw new ArgumentNullException("func", "func is null.");
            if (value == null)
                throw new ArgumentNullException("value", "value is null.");

            return (first, third) => func(first, value, third);
        }

        /// <summary>
        /// Partially applies the third argument of the specified function.
        /// </summary>
        /// <typeparam name="T1">The type of the first argument.</typeparam>
        /// <typeparam name="T2">The type of the second argument.</typeparam>
        /// <typeparam name="T3">The type of the third argument.</typeparam>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="func">The function to partially apply.</param>
        /// <param name="value">The value to apply.</param>
        /// <returns>the curried action with the second value applied.</returns>
        public static Func<T1, T2, TResult> ApplyThird<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> func, T3 value)
        {
            if (func == null)
                throw new ArgumentNullException("func", "func is null.");
            if (value == null)
                throw new ArgumentNullException("value", "value is null.");

            return (first, second) => func(first, second, value);
        }

        /// <summary>
        /// Partially applies the first argument of the specified action.
        /// </summary>
        /// <typeparam name="T1">The type of the first argument.</typeparam>
        /// <typeparam name="T2">The type of the second argument.</typeparam>
        /// <typeparam name="T3">The type of the third argument.</typeparam>
        /// <param name="action">The action to partially apply.</param>
        /// <param name="value">The value to apply.</param>
        /// <returns>The curried action with the first value applied.</returns>
        public static Action<T2, T3> ApplyFirst<T1, T2, T3>(this Action<T1, T2, T3> action, T1 value)
        {
            if (action == null)
                throw new ArgumentNullException("action", "action is null.");
            if (value == null)
                throw new ArgumentNullException("value", "value is null.");

            return (second, third) => action(value, second, third);
        }

        /// <summary>
        /// Partially applies the second argument of the specified action.
        /// </summary>
        /// <typeparam name="T1">The type of the first argument.</typeparam>
        /// <typeparam name="T2">The type of the second argument.</typeparam>
        /// <typeparam name="T3">The type of the third argument.</typeparam>
        /// <param name="action">The action to partially apply.</param>
        /// <param name="value">The value to apply.</param>
        /// <returns>The curried action with the second value applied.</returns>
        public static Action<T1, T3> ApplySecond<T1, T2, T3>(this Action<T1, T2, T3> action, T2 value)
        {
            if (action == null)
                throw new ArgumentNullException("action", "action is null.");
            if (value == null)
                throw new ArgumentNullException("value", "value is null.");

            return (first, third) => action(first, value, third);
        }

        /// <summary>
        /// Partially applies the third argument of the specified action.
        /// </summary>
        /// <typeparam name="T1">The type of the first argument.</typeparam>
        /// <typeparam name="T2">The type of the second argument.</typeparam>
        /// <typeparam name="T3">The type of the third argument.</typeparam>
        /// <param name="action">The action to partially apply.</param>
        /// <param name="value">The value to apply.</param>
        /// <returns>The curried action with the third value applied.</returns>
        public static Action<T1, T2> ApplyThird<T1, T2, T3>(this Action<T1, T2, T3> action, T3 value)
        {
            if (action == null)
                throw new ArgumentNullException("action", "action is null.");
            if (value == null)
                throw new ArgumentNullException("value", "value is null.");

            return (first, second) => action(first, second, value);
        }

        #endregion
    }
}
